import Ws           from "ws";
import Express      from "express";
import cookieParser from "cookie-parser";
import bodyParser   from "body-parser";

import PomeloLogger from "pomelo-logger";
const Logger = PomeloLogger.getLogger("Manager", process.pid, __filename);

import GamePlugin       from "./plugin/gamePlugin";
import ConnectionPlugin from "./plugin/connectionPlugin";
import HeartbeatPlugin  from "./plugin/heartbeatPlugin";
import PacketManager    from "./manager/PacketManager";

const GlobalConfig = require("../config/global.config.json");
const ManagerConfig = require("../config/manager.config.json");

class Manager extends GamePlugin {
    constructor(){
        super("Manager");

        this._host             = ManagerConfig.host;
        this._port             = ManagerConfig.port;
        this.managerServer     = null;

        this.connectionPlugin  = null;

        this._disconnectedTime = ManagerConfig.disconnectedTime * 1e3;
        this.heartbeatPlugin   = null;

        this._debugPort        = GlobalConfig.debug.addPort + this._port;
        this._debugMode        = GlobalConfig.debug.open;

        this._monitorPort      = GlobalConfig.monitor.addPort + this._port;
        this._monitorInterval  = GlobalConfig.monitor.interval;
        this.monitorServer     = null;
        this.serverStats       = {};
    }

    configure() {
        if (!this.isStatus("CREATED")) return;
        this.setStatus("INITIALIZING");

        this.setStatus("INITIALIZED");
    }
    start() {
        if (this.isStatus("CREATED")) this.configure();

        if (!this.isStatus("INITIALIZED")) return;
        this.setStatus("STARTING");

        function serverStarted() {
            // Start Main Loop
            setTimeout(this.update.bind(this), 1);

            // Server Started
            Logger.info("Server Started On [ ws://*:%s ]", this._port);

            this.connectionPlugin = new ConnectionPlugin(this);
            this.connectionPlugin.configure();
            this.connectionPlugin.start();

            this.heartbeatPlugin = new HeartbeatPlugin(this);
            this.heartbeatPlugin.configure();
            this.heartbeatPlugin.start();

            this.setStatus("STARTED");
        }
        function connectionEstablished(ws) {
            ws.remoteAddress = ws._socket.remoteAddress;
            ws.remotePort = ws._socket.remotePort;
            Logger.debug("Client Connect From [ %s ]", ws.remoteAddress);

            ws.packetManager = new PacketManager(this, ws);
            ws.packetManager.configure();
            ws.packetManager.start();

            ws.on("message", data => {ws.packetManager.handleMessage(data);});

            function close(error) {
                // Log disconnections
                Logger.info("Client Disconnect [ %s ] with Error: ", this.socket.remoteAddress, error);

                ws.packetManager.stop();
            }

            var bindObject = {server: this, socket: ws};
            ws.on("error", close.bind(bindObject));
            ws.on("close", close.bind(bindObject));
            this.connectionPlugin.connections.push(ws);
        }
        function serverErrors(e) {
            switch (e.code) {
                case "EADDRINUSE":
                    Logger.error("Server Port [ %s ] Is In Use", this.serverPort);
                    break;
                case "EACCES":
                    Logger.error("Please Run With Higher Auth");
                    break;
                default:
                    Logger.error("Unhandled error code: " + e.code);
            }
            process.exit(1); // Exits the program
        }
        this.managerServer = new Ws.Server({
            "port": this._port,
            "perMessageDeflate": false
        }, serverStarted.bind(this));
        this.managerServer.on("connection", connectionEstablished.bind(this));
        this.managerServer.on("error", serverErrors);

        this.startMonitor();
    }
    startMonitor() {
        // Do not start the server if the port is invalid
        this.monitorServer = new Express();
        this.monitorServer.use(bodyParser.json());
        this.monitorServer.use(bodyParser.urlencoded({ extended: false }));
        this.monitorServer.use(cookieParser());

        this.monitorServer.get("/", (req, res) => {
            Logger.info("[ %s ] Request [ %j ] With Args [ %s ]", req.method, req.headers, req.url);
            res.setHeader("Access-Control-Allow-Origin", "*");
            res.json(this.serverStats);
        });

        this.monitorServer.use(function(req, res, next) {
            var err = new Error('Not Found');
            err.status = 404;
            next(err);
        });

        this.monitorServer.use(function(err, req, res) {
            res.locals.message = err.message;
            res.locals.error = req.app.get('env') === 'development' ? err : {};

            res.status(err.status || 500);
            res.json(res.locals);
        });

        function httpServerOnListen(err) {
            Logger.info("Loaded stats server on port [ %s ]", this._monitorPort, err);
            setInterval(this.updateMonitor.bind(this), this._monitorInterval * 1000);
        }
        this.monitorServer.listen(this._monitorPort, httpServerOnListen.bind(this));
    }

    update() {
        setTimeout(this.update.bind(this), 1);
        if (!this.isStatus("RUNNING")) return;

        this.connectionPlugin.update();
        this.heartbeatPlugin.update();
    }
    updateMonitor () {
        this.serverStats.status = {};
        var time = new Date();
        Object.keys(this.heartbeatPlugin.socketServers).forEach(k => {
            this.serverStats.status[k] = {
                "processId": this.heartbeatPlugin.socketServers[k].processId,
                "updateAt": this.heartbeatPlugin.socketServers[k].time,
                "expired": time - this.heartbeatPlugin.socketServers[k].time > this._disconnectedTime * 1e3
            };
        });
    }

    stop() {
        if (!this.isStatus("STARTED")) return;
        this.setStatus("STOPPING");

        this.connectionPlugin.stop();
        this.heartbeatPlugin.stop();

        this.setStatus("STOPPED");
    }
}

module.exports = Manager;
export default Manager;